import Tkinter
from Tkinter import *

LEFT = "left"
RIGHT = "right"
UP = "up"
DOWN = "down"

class Point(tuple):
    #@staticmethod
    #def point(cls, k, memory={}):
    #    if memory.get(k, "e") == "e": memory[k] = tuple.__new__(cls, k)
    #    return memory[k]
    
    def __new__(cls, x, y):
        #return Point.point(cls, (x, y))
        return tuple.__new__(cls, (x, y))

    def __getattr__(self, attr):
        if attr == 'x': return self[0]
        elif attr == 'y': return self[1]
        else: raise AttributeError, attr
        
    def up(self): return Point(self.x, self.y - 1)
    def down(self): return Point(self.x, self.y + 1)
    def left(self): return Point(self.x - 1, self.y)
    def right(self): return Point(self.x + 1, self.y)

    def leftup(self): return Point(self.x - 1, self.y - 1)
    def rightup(self): return Point(self.x + 1, self.y - 1)
    def leftdown(self): return Point(self.x - 1, self.y + 1)
    def rightdown(self): return Point(self.x + 1, self.y + 1)

    def Neighbours(self):
        return [self.up(), self.down(), self.left(), self.right(), \
                self.leftup(), self.rightup(), self.leftdown(), self.rightdown()]
    
class World(object):
    def __init__(self, points = [], setDie = 1, setCreate = 3):
        self._points = points
        self.die = setDie
        self.create = setCreate
        self.colored = {} #{pt: color}

    def __getitem__(self, point):
        return self._points[point]

    def __add__(self, point):
        self.AddPoint(point)
        return self

    def __sub__(self, point):
        self.DelPoint(point)
        return self
    
    def AddPoint(self, point):
        if not point in self._points: self._points += [(point)]

    def DelPoint(self, point):
        if point in self._points: self._points.remove((point))

    def Exists(self, point):
        if point in self: return 1
        else: return 0

    def NeighbourCount(self, point):
        return reduce(lambda x, y: x + y, [0]+[1 for _ in point.Neighbours() if _ in self])

    def MoveNext(self):
        ptsAdd = []
        ptsDel = []
        for x in self._points:
            ptsAdd += [_ for _ in x.Neighbours() if _ not in self._points and self.NeighbourCount(_) == self.create]
            if self.NeighbourCount(x) not in range(self.die + 1, self.create + 1): ptsDel += [x]
        
        #return World(list(set([x for x in self._points if x not in ptsDel] + ptsAdd)), self.die, self.create)
        
        #this is slow, you should fix it to use set only
        self._points = list(set([x for x in self._points if x not in ptsDel] + ptsAdd))
    
    def PrintPoints(self):
        if self._points:
            print self._points
        else:
            print "Empty!"

    def Colorize(self, pt, color):
        self.colored[pt] = color


class Game(Frame):
    def __init__(self):
        self.world = World()
        self.BuildGrid()

    def BuildGrid(self):
        self.bonusX, self.bonusY = 0, 0
        self.stopped = True
        self.speed = 1000
        self.drawnRectangles = {}
        
        self.tk = Tk()
        self.welcome = Label(self.tk, padx = 100, pady = 10, text = "Move with Keyboard Arrows!")
        self.welcome.grid(row = 0, column = 0, columnspan = 2)

        self.canvas = Canvas(self.tk, width=305, height=305) #draw initial comp.
        self.canvas.grid(row = 1, column = 0, columnspan = 2)

        for x in range(2, 312, 10):
            self.canvas.create_line(x, 2, x, 303, fill='#808080')
            self.canvas.create_line(2, x, 303, x, fill='#808080')

        self.canvas.bind('<Button-1>', self.left)
        self.tk.bind("<Down>", lambda _: self.key_press(DOWN))
        self.tk.bind("<Up>", lambda _: self.key_press(UP))
        self.tk.bind("<Left>", lambda _: self.key_press(LEFT))
        self.tk.bind("<Right>", lambda _: self.key_press(RIGHT))

        self.strCreate = StringVar()
        self.strDie = StringVar()
        self.strSpeed = StringVar()
        
        self.enCreate = Entry(self.tk, textvariable=self.strCreate)
        self.enDie = Entry(self.tk, textvariable=self.strDie)
        self.enSpeed = Entry(self.tk, textvariable = self.strSpeed)
        
        self.strCreate.set(str(self.world.create))
        self.strDie.set(str(self.world.die))
        self.strSpeed.set(str(self.speed))

        self.lblCreate = Label(self.tk, text = "Create:")
        self.lblDie = Label(self.tk, text = "Die:")
        self.lblSpeed = Label(self.tk, text = "Step in ms:")
        
        self.lblCreate.grid(row=2, column=0)
        self.enCreate.grid(row=2, column=1)
        self.lblDie.grid(row=3, column=0)
        self.enDie.grid(row=3, column=1)
        self.lblSpeed.grid(row=4, column=0)
        self.enSpeed.grid(row=4, column=1)
        
        self.startButton = Button(self.tk, text="Start this bullshit!", pady=10, padx=15, command=self.SwitchState)
        self.startButton.grid(row=5, column=0)
        self.stopButton = Button(self.tk, text="Stop", command = self.Stop, pady=10, padx=15, state=DISABLED)
        self.stopButton.grid(row=5, column=1)

        self.clearBoard = Button(self.tk, text="Clear", command = self.Clear, pady=10, padx=15)
        self.clearBoard.grid(row=6, column=0)
        self.restoreBoard = Button(self.tk, text="Start Position", command = self.StartPosRes, pady=10, padx=15) 
        self.restoreBoard.grid(row=6, column=1)
        self.tk.mainloop()

    def key_press(self, direction):
        if direction == UP:
            self.bonusY += 10
        elif direction == LEFT:
            self.bonusX += 10
        elif direction == DOWN:
            self.bonusY -= 10 
        else:
            self.bonusX -= 10

        if self.stopped: self.Redraw()

    def left(self, event):
        x, y = (event.x/10)*10+3 - self.bonusX, (event.y/10)*10+3 - self.bonusY
        self.DrawPoint(Point(x/10,y/10), x, y)

    def DrawnRAppend(self, pt, x, y, color = 'red'):
        x += self.bonusX
        y += self.bonusY
        
        self.drawnRectangles[pt] = self.canvas.create_rectangle(x, y, x+8, y+8, fill=color, outline=color)
    
    def DrawPoint(self, pt, x = 0, y = 0):
        color = self.world.colored.get(pt, 'red')
        if (x, y) == (0, 0):
            x, y = pt.x * 10 + 3, pt.y * 10 + 3
            self.DrawnRAppend(pt, x, y, color)
        else:
            if pt in self.world:
                self.canvas.delete(self.drawnRectangles[pt])
                del self.drawnRectangles[pt]
                self.world -= pt
            else:
                self.DrawnRAppend(pt, x, y, color)
                self.world += pt

    def SwitchState(self):
        try:
            self.create = int(self.strCreate.get())
            self.die = int(self.strDie.get())
            self.speed = int(self.strSpeed.get())
            self.world.create = self.create
            self.world.die = self.die
            if self.speed<10: self.speed=10
        except:
            self.strCreate.set("3")
            self.strDie.set("1")
            self.strSpeed.set("1000")
        
        self.stopped = False
        self.StartGame()

    def Clear(self, Full = True):
        self.stopped = True
        if Full:
            self.world = World([])
        else:
            self.world = World()

            #side effect na memoization-a deto se opitah da mu sloja :)
            #ako na konstruktora ne se podade promenliva, restorva startovoto
            #sustojanie predi prednia start.
        self.Redraw()

    def StartPosRes(self): self.Clear(False)

    def Redraw(self, moveNext = False):
        for rect in self.drawnRectangles.values(): self.canvas.delete(rect)
        if moveNext: self.world.MoveNext()
        self.Colorize()
        for pt in self.world:
            self.DrawPoint(pt)
        
    def StartGame(self):
        if not self.stopped:
            self.Redraw(True)
            self.after(self.speed, self.StartGame)
            self.stopButton.config(state = NORMAL)
            self.startButton.config(state = DISABLED)

    def Stop(self):
        self.startButton.config(state = NORMAL)
        self.stopButton.config(state = DISABLED)
        self.stopped = True


    def Colorize(self):
        self.world.colored = {}
        
        for pt in self.world:
            #color example, trqbva da se napravi serijno, che taka e gyrch...
            #glider-a ima 16 systojania, za po-golemite si trqbva file s designer
            #varianti sa - s flip i rotate func, kakto
            patterns = [ 
                ([(pt, 3), (pt.up(), 3), (pt.right(),3), (pt.up().right(),3)], 'green'),
                ([(pt, 2), (pt.up(), 1), (pt.down(), 1)], 'blue'),
                ([(pt, 2), (pt.left(), 1), (pt.right(), 1)], 'blue'),
                ([(pt, 4), (pt.right(), 4), (pt.right().right(), 2), (pt.down(), 4), (pt.down().left(), 2), (pt.down().right(), 4)], 'black'),
                ([(pt, 4), (pt.left(), 4) , (pt.left().left(), 2)  , (pt.down(), 4), (pt.down().right(), 2), (pt.down().left(), 4)], 'black'),
                ([(pt, 2), (pt.up(), 1), (pt.down().right(), 1), (pt.right().right().right(), 1), (pt.right().right().right().up(), 2),
                                      (pt.right().right().up().up(), 1)], 'black'),
                ([(pt, 2), (pt.down(), 1), (pt.up().right(), 1), (pt.right().right().right(), 1), (pt.right().right().right().down(), 2),
                                      (pt.right().right().down().down(), 1)], 'black'),
                ([(pt, 4), (pt.up(), 4), (pt.up().up(), 2), (pt.left(), 4), (pt.down().left(), 2), (pt.up().left(), 4)], 'black'),
                ([(pt, 4), (pt.up(), 4), (pt.up().up(), 2), (pt.right(), 4), (pt.down().right(), 2), (pt.up().right(), 4)], 'black'),
                
                ([(pt, 2), (pt.right(), 3), (pt.right().right(), 1), (pt.down(), 3), (pt.down().down().right(), 1)], 'orange'),
                ([(pt, 2), (pt.left(), 3), (pt.left().left(), 1), (pt.down(), 3), (pt.down().down().left(), 1)], 'orange'),
                ([(pt, 2), (pt.right(), 3), (pt.right().right(), 1), (pt.up(), 3), (pt.up().up().right(), 1)], 'orange'),
                ([(pt, 2), (pt.left(), 3), (pt.left().left(), 1), (pt.up(), 3), (pt.up().up().left(), 1)], 'orange'),

                ([(pt, 2), (pt.down(), 3), (pt.down().down(), 1), (pt.right(), 3), (pt.right().right().down(), 1)], 'orange'),
                ([(pt, 2), (pt.up(), 3), (pt.up().up(), 1), (pt.right(), 3), (pt.right().right().up(), 1)], 'orange'),
                ([(pt, 2), (pt.down(), 3), (pt.down().down(), 1), (pt.left(), 3), (pt.left().left().down(), 1)], 'orange'),
                ([(pt, 2), (pt.up(), 3), (pt.up().up(), 1), (pt.left(), 3), (pt.left().left().up(), 1)], 'orange'),

                ([(pt, 2), (pt.up().left(), 2), (pt.up().up().left(), 2), (pt.up().right(), 2), (pt.up().up().right(), 2), (pt.up().up().up(), 2)], '#006699'),
                ([(pt, 2), (pt.right().up(), 2), (pt.right().right().up(), 2), (pt.right().down(), 2), (pt.right().right().down(), 2), (pt.right().right().right(), 2)], '#006699'),
                ]

            for pattern, color in patterns:
                allok = True
                
                for p, n in pattern:
                    if p not in self.world or self.world.NeighbourCount(p) <> n:
                        allok = False
                        break

                if allok:
                    for p, n in pattern:
                        self.world.Colorize(p, color)
            
g = Game()
